#include "filesystem.hpp"
#include <regex>
#include <array>
#include <opencv2/highgui/highgui.hpp>
#include "TileManagerFileSystem.h"
#include <GSLAM/core/Messenger.h>

using namespace GSLAM;

sv::Svar client;

#ifndef __linux__
#include <windows.h>

std::string GBKToUTF8(const std::string& strGBK)
{
    std::string strOutUTF8 = "";
    WCHAR * str1;
    int n = MultiByteToWideChar(CP_ACP, 0, strGBK.c_str(), -1, NULL, 0);
    str1 = new WCHAR[n];
    MultiByteToWideChar(CP_ACP, 0, strGBK.c_str(), -1, str1, n);
    n = WideCharToMultiByte(CP_UTF8, 0, str1, -1, NULL, 0, NULL, NULL);
    char * str2 = new char[n];
    WideCharToMultiByte(CP_UTF8, 0, str1, -1, str2, n, NULL, NULL);
    strOutUTF8 = str2;
    delete[]str1;
    str1 = NULL;
    delete[]str2;
    str2 = NULL;
    return strOutUTF8;
}

#endif

GSLAM::GImage getTileImage(GSLAM::TilePtr tile){
    GSLAM::GImage image=tile->getTileImage().clone();
    if(image.empty()){return image;}

    switch (image.type()) {
    case GSLAM::GImageType<uchar,3>::Type:
    {
        GSLAM::GImage wei = tile->getTileWeight();
        if(wei.empty()) {
            LOG(INFO)<<"Can't load tile weight.";
            return image;
        }
        GSLAM::GImage _img(image.rows, image.cols, 24);
        pi::Byte<3>* sp = (pi::Byte<3>*)image.data;
        pi::Byte<4>* dp = (pi::Byte<4>*)_img.data;
        uint8_t* wp = wei.data;
        for(int i=0;i<image.total();i++,sp++,dp++,wp++)
            if(*wp)
                *dp=pi::Byte<4>({sp->data[0],sp->data[1],sp->data[2],(uchar)255});
            else{
                *dp=pi::Byte<4>({(uchar)0,(uchar)0,(uchar)0,(uchar)0});
            }
        image=_img;
    }

        break;
    case GSLAM::GImageType<uchar,4>::Type:{
      for(int i=0;i<image.total();++i)
      {
        uchar& c=image.at<uchar>(4*i+3);
        if(c)
            c=255;
        else
            image.at<pi::Byte<4>>(i)=pi::Byte<4>({(uchar)0,(uchar)0,(uchar)0,(uchar)0});
      }
        break;
    }
    case GSLAM::GImageType<uint16_t,1>::Type:// multispectual
    {
        GSLAM::GImage _img(image.rows, image.cols, 24);
        std::array<uint16_t,1>* sp = (std::array<uint16_t,1>*)image.data;
        pi::Byte<4>* dp = (pi::Byte<4>*)_img.data;
        for(int i=0;i<image.total();++i,++sp,++dp){
            uint8_t b=(*sp)[0]>>8,g=(*sp)[0]>>8,r=(*sp)[0]>>8,a=(*sp)[0]?255:0;
            *dp=pi::Byte<4>({b,g,r,a});
        }
        image=_img;
    }
        break;
    case GSLAM::GImageType<uint16_t,3>::Type:// multispectual
    {
        GSLAM::GImage _img(image.rows, image.cols, 24);
        std::array<uint16_t,3>* sp = (std::array<uint16_t,3>*)image.data;
        pi::Byte<4>* dp = (pi::Byte<4>*)_img.data;
        for(int i=0;i<image.total();++i,++sp,++dp){
            uint8_t b=(*sp)[0]>>8,g=(*sp)[1]>>8,r=(*sp)[2]>>8,a=(*sp)[0]?255:0;
            *dp=pi::Byte<4>({b,g,r,a});
        }
        image=_img;
    }
        break;
    case GSLAM::GImageType<uint16_t,4>::Type:// multispectual
    {
        GSLAM::GImage _img(image.rows, image.cols, 24);
        std::array<uint16_t,4>* sp = (std::array<uint16_t,4>*)image.data;
        pi::Byte<4>* dp = (pi::Byte<4>*)_img.data;
        for(int i=0;i<image.total();++i,++sp,++dp){
            uint8_t b=(*sp)[0]>>8,g=(*sp)[1]>>8,r=(*sp)[2]>>8,a=(*sp)[0]?255:0;
            *dp=pi::Byte<4>({b,g,r,a});
        }
        image=_img;
    }
        break;
    case GSLAM::GImageType<uint16_t,5>::Type:// multispectual
    {
        GSLAM::GImage _img(image.rows, image.cols, 24);
        std::array<uint16_t,5>* sp = (std::array<uint16_t,5>*)image.data;
        pi::Byte<4>* dp = (pi::Byte<4>*)_img.data;
        for(int i=0;i<image.total();++i,++sp,++dp){
            uint8_t b=(*sp)[0]>>8,g=(*sp)[1]>>8,r=(*sp)[2]>>8,a=(*sp)[0]?255:0;
            *dp=pi::Byte<4>({b,g,r,a});
        }
        image=_img;
    }
        break;

    default:
        break;
    }

    return image;
}

bool upload_file(std::string file,std::string bucket){
    if(client.isUndefined())
    {
        return false;
    }
    bool success=client.call("PutObject","sibitu-data2",
                             bucket,file).as<bool>();

    return success;
}

sv::Svar get_oss_url(std::string bucket){
    if(client.isUndefined())
    {
        return false;
    }
    double timestamp=time(nullptr);
    return client.call("GeneratePresignedUrl","sibitu-data2",bucket,timestamp+1200);
}

bool upload_tilecache(std::string folder,std::string mapuuid){
    sv::Svar oss2=GSLAM::Registry::load("svar_oss2");
    if(oss2.isUndefined())
    {
        LOG(ERROR) <<"Unable to load plugin svar_oss2";
        return false;
    }

    sv::Svar config;
    config["endpoint"] = "http://oss-cn-zhangjiakou.aliyuncs.com";
    config["accessKeyId"] = "LTAI4FvGAsuW2MHfd3NaM1Ug";
    config["accessKeySecret"] = "hipHuCTSaYsIcS5Ehg4GM3S4oRQjoA";
    sv::Svar client = oss2["OssClient"](config);

    auto areabuf=sv::SvarBuffer::load(folder+"/area.txt");
    int64 total=areabuf.size();
    if(total<=0) {

        LOG(ERROR)<<folder+"/area.txt"<<" not exists";
        return false;
    }

    GSLAM::TileManagerPtr manager(new GSLAM::TileManagerFileSystemLZ4<GSLAM::TerrainTileWithInfo>(folder));

#ifndef __linux__
    folder = GBKToUTF8(folder);
#endif

    std::vector<Point3i> tiles;
    std::regex is_tile("^([0-9]+)_([0-9]+)_([0-9]+).tile$");
    for(auto fileit:filesystem::directory_iterator(folder))
    {
        std::smatch result;
        std::string filename = fileit.path().filename();
        if(std::regex_match(filename,result,is_tile)){
            if(result.size()<4) continue;
            int x=std::stoi(result.str(2));
            int y=std::stoi(result.str(3));
            int z=std::stoi(result.str(1));
            tiles.push_back(Point3i(x,y,z));
        }
    }

    GSLAM::detail::ThreadPool workers(std::thread::hardware_concurrency());
    GSLAM::Messenger::instance().publish("sbt.output.progress_info","Uploading tiles...");

    int step=tiles.size()/100+1;
    for(int i=0;i<tiles.size();i++)
    {
        auto id=tiles[i];
        while(workers.taskNumLeft()>std::thread::hardware_concurrency()){
            GSLAM::Rate::sleep(0.01);
            continue;
        }

        workers.Add([&workers,&manager,&client,&total,mapuuid,id](){
            std::string ext=".jpg";
            auto tile=manager->getTile(id.x,id.y,id.z);
            if(!tile)
            {
                LOG(ERROR) <<"Failed to decode tile"<<id;
                return false;
            }

            GSLAM::GImage image=getTileImage(tile);

            std::vector<uchar> buf;
            if(image.empty()||!cv::imencode(ext,cv::Mat(image),buf)){
                LOG(ERROR) <<"Failed to encode "<<id<<", matsize:"<<image.total()*image.channels();
                return false;
            }
            if(buf.empty()) return false;
            std::string tilename=std::to_string(id.z)+
                    "/"+std::to_string(id.x)+"/"+std::to_string(id.y)+ext;

            bool success=client.call("PutObject","sibitu-data2",
                        "maps/"+mapuuid+"/"+tilename,
                        sv::SvarBuffer(buf.data(),buf.size())).as<bool>();
            if(!success){
                LOG(ERROR) <<"Failed to upload "<<tilename<<" with "<<buf.size()<<" bytes.";
//                return false;
                return false;
            }
//            LOG(INFO)<<"Uploaded "<<tilename<<" with "<<buf.size()<<" bytes.";
            total+=buf.size();
        });
        if(i%step==0)
            GSLAM::Messenger::instance().publish("sbt.output.progress_percent",i/double(tiles.size()));
    }

    bool success=client.call("PutObject","sibitu-data2",
                             "maps/"+mapuuid+"/area.txt",areabuf).as<bool>();
    if(!success){
        LOG(ERROR) <<"Failed to upload area.txt";
        return false;
    }

    LOG(INFO)<<"Successfully uploaded map "<<mapuuid<<" with size "<<total<<"bytes";
    GSLAM::Messenger::instance().publish("sbt.output.progress_percent",1.0);
    return true;
}

bool convert_to_jpeg(std::string src,std::string target){
    std::ifstream ifs(src,std::ios::in|std::ios::binary);

    if(!ifs.is_open()) return false;
    char isCompressed=true;
    ifs.read(&isCompressed,1);
    // Uncompress here
    std::string Inbuf( (std::istreambuf_iterator<char>(ifs)) , std::istreambuf_iterator<char>() );
    if(Inbuf.size()==0) return false;
    std::stringstream sst;

    if(isCompressed){
        std::vector<char> buf(2e6);
        int buf_len = LZ4_decompress_safe(Inbuf.data(), buf.data(), Inbuf.size(), buf.size());
        sst.str(std::string(buf.data(),buf_len));
    }
    else
        sst.str(Inbuf);

    TilePtr tile(new GSLAM::TerrainTileWithInfo());
    if(!tile->fromStream(sst))
    {
      LOG(ERROR)<<"Obtained buffer but can't load tile. compressed "<<isCompressed;
      return false;
    }

    GSLAM::GImage image=getTileImage(tile);

    if(image.empty()){
        LOG(ERROR) <<"Failed to encode , matsize:"<<image.total()*image.channels();
        return false;
    }

    return cv::imwrite(target,cv::Mat(image));
}

bool convert_to_jpegfolder(std::string folder,std::string target_folder){
    auto areabuf=sv::SvarBuffer::load(folder+"/area.txt");
    int64 total=areabuf.size();
    if(total<=0) {

        LOG(ERROR)<<folder+"/area.txt"<<" not exists";
        return false;
    }

    GSLAM::TileManagerPtr manager(new GSLAM::TileManagerFileSystemLZ4<GSLAM::TerrainTileWithInfo>(folder));

#ifndef __linux__
    folder = GBKToUTF8(folder);
#endif

    std::vector<Point3i> tiles;
    std::regex is_tile("^([0-9]+)_([0-9]+)_([0-9]+).tile$");
    for(auto fileit:filesystem::directory_iterator(folder))
    {
        std::smatch result;
        std::string filename = fileit.path().filename();
        if(std::regex_match(filename,result,is_tile)){
            if(result.size()<4) continue;
            int x=std::stoi(result.str(2));
            int y=std::stoi(result.str(3));
            int z=std::stoi(result.str(1));
            tiles.push_back(Point3i(x,y,z));
        }
    }

    GSLAM::detail::ThreadPool workers(std::thread::hardware_concurrency());
    GSLAM::Messenger::instance().publish("sbt.output.progress_info","Uploading tiles...");

    int step=tiles.size()/100+1;
    for(int i=0;i<tiles.size();i++)
    {
        auto id=tiles[i];
        while(workers.taskNumLeft()>std::thread::hardware_concurrency()){
            GSLAM::Rate::sleep(0.01);
            continue;
        }

        workers.Add([&workers,&manager,&total,id,target_folder](){
            std::string ext=".jpg";
            auto tile=manager->getTile(id.x,id.y,id.z);
            if(!tile)
            {
                LOG(ERROR) <<"Failed to decode tile"<<id;
                return false;
            }

            GSLAM::GImage image=getTileImage(tile);

            std::vector<uchar> buf;
            if(image.empty()){
                LOG(ERROR) <<"Failed to encode "<<id<<", matsize:"<<image.total()*image.channels();
                return false;
            }
            std::string tilename=std::to_string(id.z)+
                    "_"+std::to_string(id.x)+"_"+std::to_string(id.y)+ext;

            cv::imencode(ext, cv::Mat(image),buf);
            sv::SvarBuffer(buf.data(),buf.size()).save(target_folder+"/"+tilename);
//            LOG(INFO)<<"Uploaded "<<tilename<<" with "<<buf.size()<<" bytes.";
            total+=buf.size();
            return true;
        });
        if(i%step==0)
            GSLAM::Messenger::instance().publish("sbt.output.progress_percent",i/double(tiles.size()));
    }

    LOG(INFO)<<"Successfully uploaded map "<<target_folder<<" with size "<<total<<"bytes";
    GSLAM::Messenger::instance().publish("sbt.output.progress_percent",1.0);
    return true;
}

REGISTER_SVAR_MODULE(upload){
    jvar["upload_tilecache"]=upload_tilecache;
    jvar["upload_file"]=upload_file;
    jvar["get_oss_url"]=get_oss_url;
    jvar["convert_to_jpeg"]=convert_to_jpeg;
    jvar["convert_to_jpegfolder"]=convert_to_jpegfolder;
}

EXPORT_SVAR_INSTANCE
